/*
 * Though originally coded by Eli Gottlieb in the year 2006, this code rests in the public domain.
 * This code based on information found at http://www.osdev.org/osfaq2/index.php/SerialPorts 
 * It's a proof-of-concept for EDI, don't expect professional quality code. */

/* Edited by thePowersGang (John Hodge) June 2009
 * - Added better commenting & formatting
 * - Resolve overloading of serial_port_read
 * - replace 'unsigned int32_t' with 'uint32_t' to fix error on GCC
 */
#define EDI_MAIN_FILE
#include "edi.h"
#include "helpers.h"

// === CONSTANTS ===
#define BASE_INTERRUPT	4
//#define BASE_PORT	0x3F8
#define BASE_PORT	0x2F8	// COM2 - Removes collision with Acess Debugging port

// === STRUCTURES ===
typedef struct {
	bool	free;
	int8_t	*received_chars;
	int32_t	num_received_chars;
} serial_port_t;

// === GLOBALS ===
edi_object_metadata_t	interrupt;
edi_object_metadata_t	ports[6];
serial_port_t	com_one = {true, NULL, 0};
edi_class_declaration_t	serial_port_class;
edi_function_declaration_t	serial_port_methods[3];

// === CODE ===
/**
 * \fn object_pointer make_serial_port()
 * \brief Create an object of the serial port
 */
object_pointer make_serial_port()
{
	if(com_one.free == true)
	{
		com_one.free = false;
		write_byte_io_port( &ports[1], 0x00 );
		write_byte_io_port( &ports[3], 0x80 );
   		write_byte_io_port( &ports[0], 0x03 );
		write_byte_io_port( &ports[1], 0x00 );
		write_byte_io_port( &ports[3], 0x03 );
		write_byte_io_port( &ports[2], 0xC7 );
		write_byte_io_port( &ports[4], 0x0B );
		return (object_pointer)&com_one;
	}
	else
		return NULL;
}

/**
 * \fn void destroy_serial_port(object_pointer serial_port)
 * \brief Free the serial port object
 */
void destroy_serial_port(object_pointer serial_port)
{
	if(serial_port == &com_one)
		((serial_port_t*)serial_port)->free = true;
}

/**
 * \fn int32_t driver_quota(edi_string_t class)
 * \note Required by EDI
 */
int32_t driver_quota(edi_string_t class)
{
	if(edi_string_equal(class, chardev_class.name))
		return 1;
	else
		return 0;
}

/**
 * \fn void serial_port_read_in(serial_port_t *serial_port)
 * \brief Read from the serial port to the memeory buffer
 * \note Was named `serial_port_read`, changed to remove conflict
 */
void serial_port_read_in(serial_port_t *serial_port)
{
	serial_port->num_received_chars++;
	serial_port->received_chars = realloc(serial_port->received_chars, serial_port->num_received_chars);
	read_byte_io_port( &ports[0], &serial_port->received_chars[serial_port->num_received_chars - 1] );
}

/**
 * \fn int32_t serial_port_read(serial_port_t *serial_port, data_pointer buffer, uint32_t count)
 * \brief Read data from the serial buffer to the system
 * \param serial_port	Port object to read from
 * \param buffer	Destination buffer
 * \param count		Number of bytes to read
 */
int32_t serial_port_read(serial_port_t *serial_port, data_pointer buffer, uint32_t count)
{
	if(serial_port != &com_one)
		return EBADOBJ;
	if(serial_port->num_received_chars >= count)
	{
		memcpy(buffer, serial_port->received_chars, count);
		memmove(
			serial_port->received_chars,
			&serial_port->received_chars[count],
			serial_port->num_received_chars - count);
		serial_port->num_received_chars -= count;
		serial_port->received_chars = realloc(serial_port->received_chars, serial_port->num_received_chars);
		return 1;
	}
	else
		return EAGAIN;
}

/**
 * \fn int32_t serial_port_write(serial_port_t *serial_port, data_pointer buffer, uint32_t count)
 * \brief Write data to the serial port
 * \param serial_port	Port object to write to
 * \param buffer	Destination buffer
 * \param count		Number of bytes to write
 */
int32_t serial_port_write(serial_port_t *serial_port, data_pointer buffer, uint32_t count)
{
	 int	i;
	int8_t	byte;
	if(serial_port != &com_one)
		return EBADOBJ;
	for(i=0; i<count; i++)
	{
		while( read_byte_io_port(&ports[5], &byte), (byte & 0x20) == 0 ) ;
		write_byte_io_port( &ports[0], ((int8_t*)buffer)[i] );
	};
	return 1;
}

/**
 * \fn int32_t serial_port_select(serial_port_t *serial_port, uint32_t select_type)
 * \brief Unknown Action
 */
int32_t serial_port_select(serial_port_t *serial_port, uint32_t select_type)
{
	int8_t	byte;
	if(serial_port != &com_one)
		return EBADOBJ;
	switch(select_type)
	{
	case EDI_SELECT_WRITABLE:
		while( read_byte_io_port(&ports[5], &byte), (byte & 0x20) == 0 ) ;
		break;
	case EDI_SELECT_READABLE:
		while( read_byte_io_port(&ports[5], &byte), (byte & 1) == 0 ) ;
		break;
	
	default:
		return EINVAL;
	}
	return 0;
}

/**
 * \fn void serial_port_interrupt(uint32_t irq)
 * \brief Serial IRQ Handler
 */
void serial_port_interrupt(uint32_t irq)
{
	int8_t	byte;
	read_byte_io_port(&ports[5], &byte);
	if( (byte & 1) == 1 )
	{
		com_one.num_received_chars ++;
		com_one.received_chars = realloc(com_one.received_chars, com_one.num_received_chars);
	}
	
	interrupt_return( &interrupt );
}

/**
 * \fn edi_initialization_t driver_init(int32_t num_resources, edi_object_metadata_t *resources)
 * \brief Driver Entrypoint - Initialises the serial port
 * \param num_resources	Number of resources passed
 * \param resources	Resources that the implementation has offered
 */
edi_initialization_t driver_init(int32_t num_resources, edi_object_metadata_t *resources)
{
	 int	i;
	data_pointer	class_interrupts;
	data_pointer	class_ports;
	edi_initialization_t	ret = {0, NULL, NULL, "serial-port", "", 0, 0};
	
	// Obtain EDI function pointers to needed methods.
	class_interrupts = get_actual_class( (char*)interrupts_class, num_resources, resources );
	// Get pointers to the I/O port class's methods.
	class_ports = get_actual_class( (char*)io_port_class, num_resources, resources );
	
	if(class_interrupts == NULL)
		class_interrupts = get_internal_class( (char*)interrupts_class);
	if(class_ports == NULL)
		class_ports = get_internal_class( (char*)io_port_class);
	
	// Get pointers to the interrupt class's methods.
	interrupt_get_irq = (void *) get_method_by_name(class_interrupts, "interrupt_get_irq");
	interrupt_set_handler = (void *) get_method_by_name(class_interrupts, "interrupt_set_handler");
	interrupt_return = (void *) get_method_by_name(class_interrupts, "interrupt_return");
	
	// Get port class's methods
	get_port_number = (void *) get_method_by_name(class_ports, "get_port_number");
	read_byte_io_port = (void *) get_method_by_name(class_ports, "read_byte_io_port");
	write_byte_io_port = (void *) get_method_by_name(class_ports, "write_byte_io_port");
	
	/* Attempt to sort the driver's given resources into the needed interrupts and ports. */
	for(i=0;i<num_resources;i++)
	{
		if(resources[i].object_class == class_interrupts)
		{
			uint32_t	irq = interrupt_get_irq(resources[i].object);
			if(irq == BASE_INTERRUPT)
				interrupt = resources[i];
		}
		else if(resources[i].object_class == class_ports)
		{
			uint16_t	port = get_port_number(resources[i].object);
			if((port - BASE_PORT >= 0) && (port - BASE_PORT < 6))
				ports[port - BASE_PORT] = resources[i];
		}
	}
	
	/* Run through the driver's resources, attempting to acquire what it has not received. */
	init_interrupt = (void *) get_method_by_name(class_interrupts, "init_interrupt");
	if(interrupt.object == NULL)
	{
		interrupt = construct_object( (char*)interrupts_class );
		if((interrupt.object == NULL) || 
		   (init_interrupt(interrupt.object, BASE_INTERRUPT, NULL) != 1))
				return ret;
	}

	init_io_port = (void *) get_method_by_name(class_ports, "init_io_port");
	for(i=0;i<6;i++)
	{
		if(ports[i].object == NULL)
		{
			ports[i] = construct_object( (char*)io_port_class );
			if((ports[i].object == NULL) || (init_io_port(ports[i].object, BASE_PORT + i) != 1))
				return ret;
		}
	}
	
	/* Set the IRQ handler for IRQ 4. */
	interrupt_set_handler( &interrupt, serial_port_interrupt );
	
	// Create serial port class
	memcpy( &serial_port_class, &chardev_class, sizeof(edi_class_declaration_t) );
	serial_port_class.num_methods = 3;
	serial_port_class.methods = serial_port_methods;
	serial_port_class.constructor = &make_serial_port;
	serial_port_class.destructor = &destroy_serial_port;
	// Create serial port method declarations
	memcpy(serial_port_methods, serial_port_class.methods, sizeof(edi_function_declaration_t)*2);
	serial_port_methods[0].code = (function_pointer) serial_port_read;
	serial_port_methods[1].code = (function_pointer) serial_port_write;
	serial_port_methods[2] = select_declaration;
	serial_port_methods[2].code = (function_pointer) serial_port_select;
	
	// Fill return structure
	ret.num_driver_classes = 1;
	ret.driver_classes = &serial_port_class;
	ret.k_quota = &driver_quota;
	
	return ret;
}

/**
 * \fn void driver_finish()
 * \brief Clean up driver resources
 * \note Called on shutdown or driver unload
 */
void driver_finish()
{
	int i;
	//for(i=0;i<2;i++)
	//	destroy_object( interrupts[i] );
	destroy_object( interrupt );
	for(i=0;i<5;i++)
		destroy_object(ports[i]);
};
